#!/usr/bin/env python

import random
import sys
import time

GENE_POOL = 'abcdefghijklmnopqrstuvwxyz'
DEAD_DNA = '.'

class LiveCell(object):
    dead = False

    def __init__(self, dna=None):
        if dna is None:
            dna = random.choice(GENE_POOL)
        self.dna = dna

    def __str__(self):
        return self.dna

    def new_dna(self, cells):
        return random.choice([x.dna for x in cells if not x.dead])

    def evolve(self, neighbours=[]):
        live_neighbours = [x for x in neighbours if not x.dead]
        if 2 <= len(live_neighbours) <= 3:
            next_cell = LiveCell(dna=self.dna)
        else:
            next_cell = DeadCell()
        return next_cell


class DeadCell(object):
    dead = True

    def __str__(self):
        return str(DEAD_DNA)

    def new_dna(self, cells):
        return random.choice([x.dna for x in cells if not x.dead])

    def evolve(self, neighbours=[]):
        live_neighbours = [x for x in neighbours if not x.dead]
        if len(live_neighbours) == 3:
            next_cell = LiveCell(dna=self.new_dna(neighbours))
        else:
            next_cell = DeadCell()
        return next_cell


class MainBoard(object):
    
    def __init__(self, width=64, height=16, initial_cell=DeadCell):
        self.height = height
        self.width = width
        self.init_cells(initial_cell)

    def __str__(self):
        return '\n'.join([''.join([str(self.cells[x][y]) for x in range(self.width)])
                                    for y in range(self.height)])

    def init_cells(self, initial_cell):
        self.cells = [[initial_cell() for y in range(self.height)]
                                        for x in range(self.width)]
        return self.cells

    def populate(self, cells, density=0.618):
        for x in range(self.width):
            for y in range(self.height):
                if random.random() > density:
                    self.cells[x][y] = random.choice(cells)()
        return self.cells

    @property
    def all_cells(self):
        return (self.cells[x][y] for x in range(self.width) for y in range(self.height))

    @property
    def all_dead(self):
        return all(x.dead for x in self.all_cells)
    
    def get_neighbours_locs(self, x_pos, y_pos):
        curr_pos = y_pos * self.width + x_pos
        locs = [(x_pos + x, y_pos + y) for x in range(-1, 2) for y in range(-1, 2)]
        locs = filter(lambda l: 0 <= l[0] < self.width and 0 <= l[1] < self.height, locs)
        locs.remove((x_pos, y_pos))
        return locs

    def get_neighbours(self, x_pos, y_pos):
        return [self.cells[x][y] for x, y in self.get_neighbours_locs(x_pos, y_pos)]

    def evolve(self, times=1):
        for i in range(times):
            new_cells = []
            for x in range(self.width):
                new_cells.append([self.cells[x][y].evolve(self.get_neighbours(x, y))
                                    for y in range(self.height)])
            self.cells = new_cells
        return self.cells

    def dumps(self):
        return str(self)

    def loads(self, data, set_size=False):
        rows = data.splitlines()
        if set_size:
            self.width = len(rows[0])
            self.height = len(rows)
            self.init_cells(DeadCell)
        for y, row in enumerate(rows):
            for x, dna in enumerate(row):
                self.cells[x][y] = LiveCell(dna=dna) if dna != DEAD_DNA else DeadCell()
        return self.cells


if __name__ == '__main__':
    b = MainBoard()
    if len(sys.argv) > 1:
        with open(sys.argv[1]) as i:
            b.loads(i.read(), set_size=True)
    else:
        b.populate([LiveCell])
    generation = 0
    stable = False
    while not stable:
        print 'Generation: %06d' % generation
        print b
        print '-' * (b.width + 2)
        stable = str(b.cells) == str(b.evolve())
        generation += 1
        time.sleep(0.5)
